/*
 *  Copyright 2010 Mahbub Mozadded Sowrov.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package com.sowrov.util.logsystem;


/**
 * Singleton ChronicleLogger.
 *
 * @author Sowrov
 * @version %I% %G%
 */
public final class ChronicleLogger {
    private LevelManager levelManager;
    private LogFormatter logFormatter;
    private OutputDevice outputDevice;

    /**
     * Inner class to construct our ChronicleLogger object once.
     */
    private static class ChronicleLoggerHolder {
        private static ChronicleLogger INSTANCE = new ChronicleLogger();
    } // end ChronicleLoggerHolder

    /**
     * Shutdown hook, so that we can log the time when the application was terminated.
     */
    private static class ShutdownHook extends Thread {
        @Override
        public void run() {
            try {
                ChronicleLoggerHolder.INSTANCE.dispose();
            } // end try
            catch (final Throwable ex) {
            } // end catch
        } // end run()
    } // end ShutdownHook

    /**
     * Disabled default constructor.
     */
    private ChronicleLogger() {
        this.levelManager = new LevelManager();
        this.logFormatter = new PlainText();
        this.outputDevice = new Console();
        Runtime.getRuntime().addShutdownHook(new ShutdownHook()); //add the shutdown hook :)
    } // end ChronicleLogger()

    /**
     * Get singleton ChronicleLogger object.
     *
     * @return ChronicleLogger object
     */
    public static ChronicleLogger getInstance() {
        return ChronicleLoggerHolder.INSTANCE;
    } // end getInstance()

    /**
     * Set a device to the current logger
     *
     * @param device
     */
    public void setDevice(final OutputDevice device) {
        if (this.outputDevice != null) {
            if ((this.logFormatter != null) && !this.outputDevice.isFreshDevice()) {
                this.outputDevice.write(this.logFormatter.getFinalFormat());
            } // end if

            this.outputDevice.flush();
            this.outputDevice.close();
        } // end if

        this.outputDevice = device;
        this.outputDevice.init();
    } // end setDevice()

    /**
     * set Log level
     *
     * @param level
     */
    public void setLevel(final LogLevel level) {
        this.levelManager.setLevel(level);
    } // end setLevel()

    /**
     * set a formatter for the logger
     *
     * @param format
     */
    public void setLogFormatter(final LogFormatter format) {
        this.logFormatter = format;
    } // end setLogFormatter()

    /**
     * Log a message, for backward compatibility with old logger.
     *
     * @param logType level of this message
     * @param message the string message
     */
    public void log(final String logType, final String message) {
        LogLevel logLevel = LevelManager.findLogLevel(logType);
        this.log(message, logLevel, 2);
    } // end log()

    /**
     * Critical: Application fall in to a serious error but it can still go forward
     *
     * @param message the string message
     */
    public void logCritical(final String message) {
        this.log(message, LogLevel.CRITICAL, 2);
    } // end logCritical()

    /**
     * Log any kind of message that will help to debug the app
     *
     * @param message the string message
     */
    public void logDebug(final String message) {
        this.log(message, LogLevel.DEBUG, 2);
    } // end logDebug()

    /**
     * Fatal: A very critical error which cause the application to terminate.
     *
     * @param message the string message
     */
    public void logFatal(final String message) {
        this.log(message, LogLevel.FATAL, 2);
    } // end logFatal()

    /**
     * Info: Any information message.
     *
     * @param message the string message
     */
    public void logInfo(final String message) {
        this.log(message, LogLevel.INFO, 2);
    } // end logInfo()

    /**
     * Warning: Warning message for the user
     *
     * @param message the string message
     */
    public void logWarning(final String message) {
        this.log(message, LogLevel.WARNING, 2);
    } // end logWarning()

    protected void dispose() {
        if (this.outputDevice != null) {
            if ((this.logFormatter != null) && !this.outputDevice.isFreshDevice()) {
                this.outputDevice.write(this.logFormatter.getFinalFormat());
            } // end if

            this.outputDevice.close();
        } // end if

        this.outputDevice = null;
        this.logFormatter = null;
    } // end dispose()

    /**
     * Is the given level is enabled by the current level.
     *
     * @param level given level
     *
     * @return true/false base one enabled or disabled
     */
    private boolean isEnableLevel(final LogLevel level) {
        return this.levelManager.isEnableLevel(level);
    } // end isEnableLevel()

    private synchronized void log(final String message, final LogLevel logLevel, final int traceBack) {
        if (this.isEnableLevel(logLevel)) {
            if (this.outputDevice == null) {
                this.setDevice(new Console());
            } // end if

            if (this.outputDevice.isFreshDevice()) {
                this.outputDevice.write(this.logFormatter.getIntroFormat());
            } // end if

            String callerFunction = "";
            String callerClass = "";
            String fileName = "";
            int lineNo = -1;

            try {
                throw new Throwable();
            } // end try
            catch (final Throwable t) {
                StackTraceElement[] stack = t.getStackTrace();
                fileName = stack[traceBack].getFileName();
                callerClass = stack[traceBack].getClassName();
                callerFunction = stack[traceBack].getMethodName();
                lineNo = stack[traceBack].getLineNumber();
            } // end catch

            switch (logLevel) {
                case FATAL:
                    this.outputDevice.write(
                        this.logFormatter.getFatalFormat(
                            message,
                            lineNo,
                            callerClass + "." + callerFunction,
                            fileName));

                    break;
                case CRITICAL:
                    this.outputDevice.write(
                        this.logFormatter.getCriticalFormat(
                            message,
                            lineNo,
                            callerClass + "." + callerFunction,
                            fileName));

                    break;
                case WARNING:
                    this.outputDevice.write(
                        this.logFormatter.getWarningFormat(
                            message,
                            lineNo,
                            callerClass + "." + callerFunction,
                            fileName));

                    break;
                case INFO:
                    this.outputDevice.write(
                        this.logFormatter.getInfoFormat(
                            message,
                            lineNo,
                            callerClass + "." + callerFunction,
                            fileName));

                    break;
                default:
                    this.outputDevice.write(
                        this.logFormatter.getDebugFormat(
                            message,
                            lineNo,
                            callerClass + "." + callerFunction,
                            fileName));
            } // end switch
        } // end if
    } // end log()
} // end ChronicleLogger
